#-*- coding: utf-8 -*-
"""
    Leonardo
    ~~~~~~
    A simple server for providing web interface to upload files using qiniu
    :copyright: (c) 2015 by Wilson.
"""

from flask import Flask, jsonify, request, render_template
from qiniu import Auth, BucketManager, build_batch_delete, build_batch_move, put_data
import logging
from logging import Formatter
from logging.handlers import TimedRotatingFileHandler

import json
from functools import wraps
from flask import redirect, request, current_app

def support_jsonp(f):
    """Wraps JSONified output for JSONP"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        callback = request.args.get('callback', False)
        if callback:
            content = str(callback) + '(' + str(f().data) + ')'
            return current_app.response_class(content, mimetype='application/json')
        else:
            return f(*args, **kwargs)
    return decorated_function


app = Flask(__name__)
app.config.from_object('config')
auth = Auth(app.config['ACCESS_KEY'], app.config['SECRET_KEY'])
bucket_manager = BucketManager(auth)
logger = logging.getLogger('Leonardo')


class Result:

  def __init__(self):
    self.code = '1'

  def fail(self):
    self.code = '1'
    return self

  def success(self):
    self.code = '0'
    return self

  def data(self, data):
    self.data = data
    return self

  def set_message(self, message):
    self.message = message
    return self

  def to_dict(self):
    return self.__dict__


"""
URL
      /upload-frame     get upload frame view
"""
@app.route("/upload-frame")
def upload_frame():
  root_domain = request.args.get('root_domain', app.config['ROOT_DOMAIN'])
  return render_template('upload-frame.html', temp_domain=app.config['QINIU_TEMP_DOMAIN'], prod_domain=app.config['QINIU_PROD_DOMAIN'], domain=app.config['DOMAIN'], root_domain=root_domain, prod_bucket=app.config['BUCKET_PROD'])


"""
URL
      /upload-token     supports jsonp by sending parameter 'callback'

PARAM
      key:        string; optional, not recomended; if key is given, key is used as file name and IMPORTANTLY, you still have to post key when uploading your file, or default file name is '{prefix}{hash}.{ext}'
      prefix:     string; optional; if param key is specified, prefix will be ignore; or file name will be '{prefix}{hash}.{ext}'
      bucket:     string; optional; the domain name of qiniu, if not specified, use default BUCKET_TEMP configued
"""
@app.route("/upload-token")
@support_jsonp
def upload_token():
  key = request.args.get('key')
  prefix = request.args.get('prefix', '')
  bucket = request.args.get('bucket', app.config['BUCKET_TEMP'])
  policy = {'saveKey': prefix+"$(etag)$(ext)"}
  token = auth.upload_token(bucket, key, 7200, policy)
  return jsonify({'token': token})


"""
URL
      /move
PARAM
      bucket:       string; optional; if not specified, use default BUCKET_TEMP configued      
      key:          string; required; origin file name
      new_bucket:   string; optional; if not specified, use default BUCKET_PROD configued
      new_key:      string; optional; if not specified, use originKey
    TODO
      Exception handle
"""
@app.route("/move", methods=['POST', 'GET'])
def move():
  if request.method == 'POST':
    origin_bucket = request.form.get('bucket', app.config['BUCKET_TEMP'])
    origin_key = request.form.get('key')
    new_bucket =  request.form.get('new_bucket', app.config['BUCKET_PROD'])
    new_key = request.form.get('new_key', origin_key)
  else:
    origin_bucket = request.args.get('bucket', app.config['BUCKET_TEMP'])
    origin_key = request.args.get('key')
    new_bucket =  request.args.get('new_bucket', app.config['BUCKET_PROD'])
    new_key = request.args.get('new_key', origin_key)

  result = Result()

  logger.info('Move Begin: origin_bucket[{0}], origin_key[{1}], new_bucket[{2}], new_key[{3}]'.format(origin_bucket, origin_key, new_bucket, new_key))
  ret, info = bucket_manager.move(origin_bucket, origin_key, new_bucket, new_key)
  logger.info('Move RET Result: {0}'.format(ret))
  logger.info('Move Response Info: {0}'.format(info))

  if info.status_code == 200:
    result.success().set_message('successfully moved')
  elif info.status_code == 614: # think file exists as success
    result.success().set_message('file exists')
  else:
    result.fail().set_message(info.error)
  return jsonify(result.to_dict())


"""
URL
      /move-multiple
PARAM
      bucket:       string; optional; if not specified, use default BUCKET_TEMP configued      
      keys:         string[]; required; origin file names
      new_bucket:   string; optional; if not specified, use default BUCKET_PROD configued
      new_keys:     string[]; optional; if not specified, use origin keys
    TODO
      Exception handle
"""
@app.route("/move-multiple", methods=['POST', 'GET'])
def move_multiple():
  if request.method == 'POST':
    origin_keys = request.form.get('keys', '').split(',')
    new_keys = request.form.get('new_keys', request.form.get('keys', '')).split(',')
    origin_bucket = request.form.get('bucket', app.config['BUCKET_TEMP'])
    new_bucket =  request.form.get('new_bucket', app.config['BUCKET_PROD'])
  else:
    origin_keys = request.args.get('keys', '').split(',')
    new_keys = request.args.get('new_keys', request.args.get('keys', '')).split(',')
    origin_bucket = request.args.get('bucket', app.config['BUCKET_TEMP'])
    new_bucket =  request.args.get('new_bucket', app.config['BUCKET_PROD'])

  result = Result()

  key_pairs = {}
  for key, new_key in zip(origin_keys, new_keys):
    key_pairs[key] = new_key

  logger.info('Move-multiple Begin: origin_bucket[{0}], key_pairs[{1}], new_bucket[{2}]'.format(origin_bucket, key_pairs, new_bucket))
  ops = build_batch_move(origin_bucket, key_pairs, new_bucket)
  ret, info = bucket_manager.batch(ops)

  logger.info('Move-multiple RET Result: {0}'.format(ret))
  logger.info('Move-multiple Response Info: {0}'.format(info))

  if info.status_code == 200:
    result.success().set_message('all successfully moved')
  elif info.status_code == 298: # 部分或全部失败
    partial_success = True
    for item in json.loads(info.text_body):
      if item[u'code'] != 614 and item[u'code'] != 200:
        partial_success = False
    if partial_success:
      result.success().set_message('some file exists')
    else:
      result.fail().set_message(info.text_body)
  else:
    result.fail().set_message(info.error)
  return jsonify(result.to_dict())



"""
URL
      /delete
PARAM
      bucket:   string; optional; if not specified, use default BUCKET_TEMP configued      
      key:      string; required; file name to delete
    TODO
      Exception handle
"""
@app.route("/delete", methods=['POST', 'GET'])
def delete():
  if request.method == 'POST':
    bucket = request.form.get('bucket', app.config['BUCKET_TEMP'])
    key = request.form.get('key')
  else:
    bucket = request.args.get('bucket', app.config['BUCKET_TEMP'])
    key = request.args.get('key')

  result = Result()

  logger.info('Delete Begin: bucket[{0}], key[{1}]'.format(bucket, key))
  ret, info = bucket_manager.delete(bucket, key)
  logger.info('Delete RET Result: {0}'.format(ret))
  logger.info('Delete Response Info: {0}'.format(info))

  if info.status_code == 200:
    result.success().set_message('successfully deleted')
  else:
    result.fail().set_message(info.error)
  return jsonify(result.to_dict())


"""
URL
      /delete-all
PARAM
      bucket:     string; optional; if not specified, use default BUCKET_TEMP configued      
      prefix:     string; optional; key prefix
    TODO
      Exception handle
"""
@app.route("/delete-all", methods=['POST', 'GET'])
def delete_all():
  if request.method == 'POST':
    bucket = request.form.get('bucket', app.config['BUCKET_TEMP'])
    prefix = request.form.get('prefix')
  else:
    bucket = request.args.get('bucket', app.config['BUCKET_TEMP'])
    prefix = request.args.get('prefix')

  marker = None
  eof = False
  keys = []
  result = Result()

  logger.info('Delete All Begin: bucket[{0}], prefix[{1}]'.format(bucket, prefix))
  # read all file names into keys
  while eof is False:
    ret, eof, info = bucket_manager.list(bucket, prefix=prefix, marker=marker)
    marker = ret.get('marker', None)
    for item in ret['items']:
      logger.info('found item: ' + item['key'])
      keys.append(item['key'])

  # delete all
  ops = build_batch_delete(bucket, keys)
  ret, info = bucket_manager.batch(ops)

  logger.info('Delete All RET Result: {0}'.format(ret))
  logger.info('Delete All Response Info: {0}'.format(info))
  if info.status_code == 200:
    result.success().set_message('successfully delete all')
  else:
    result.fail().set_message(info.error)

  return jsonify(result.to_dict())


"""
URL
      /generate-qrcode POST PARAM
      url:     string; required; target url to generate qrcode
      bucket:     string; optional; if not specified, use default BUCKET_PROD configued
    TODO
      Exception handle
"""
@app.route("/generate-qrcode", methods=['POST'])
def generate_qrcode():
  bucket = request.form.get('bucket', app.config['BUCKET_PROD'])
  url = request.form.get('url')
  prefix = 'qrcode/'
  policy = {'saveKey': prefix+"$(etag)"}
  result = Result()
  up_token = auth.upload_token(bucket = bucket, expires = 7200, policy = policy)
  ret, info = put_data(up_token = up_token, key = None, data = url)
  logger.info('Generate qrcode RET Result: {0}'.format(ret))
  logger.info('Generate qrcode Response Info: {0}'.format(info))
  if info.status_code == 200:
    result.success().set_message('successfully generated').data({'key': ret['key'] + '?qrcode/1'})
  else:
    result.fail().set_message(info.error)
  return jsonify(result.to_dict())

"""
URL
      /fetch      POST PARAM
      url:        string; required; target url
      bucket:     string; optional; if not specified, use default BUCKET_PROD configued
      key:        string; optional; if not specified, key is default to be hash
    TODO
      Exception handle
"""
@app.route("/fetch", methods=['POST'])
def fetch():
  bucket = request.form.get('bucket', app.config['BUCKET_PROD'])
  url = request.form.get('url')
  key = request.form.get('key', None)
  result = Result()
  ret, info = bucket_manager.fetch(url, bucket, key)
  logger.info('Generate qrcode RET Result: {0}'.format(ret))
  logger.info('Generate qrcode Response Info: {0}'.format(info))
  if info.status_code == 200:
    result.success().set_message('successfully fetched').data({'key': ret['key']})
  else:
    result.fail().set_message(info.error)

  return jsonify(result.to_dict())


def init_logger():
  logger = logging.getLogger('Leonardo')
  handler = TimedRotatingFileHandler(app.config['LOG_FILE'], when="midnight")
  handler.suffix = '%Y-%m-%d'
  handler.setFormatter(Formatter(
    '%(asctime)s %(module)s %(levelname)s: %(message)s '
    '[in %(pathname)s:%(lineno)d]'
  ))
  app.logger.addHandler(handler)
  app.logger.setLevel(logging.INFO)
  logger.addHandler(handler)
  logger.setLevel(logging.INFO)

#init_logger()

if __name__ == '__main__':
  app.run(port=9090, debug=True)


